# ClientConnection - This connects socket client to backend's message bus.
# Copyright (C) 2007 Lauri Taimila
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

__licence__ = "GPLv2"
__copyright__ = "2007, Lauri Taimila"
__author__ = "Lauri Taimila <lauri@taimila.com>"

import socket
import cPickle
from cStringIO import StringIO
import threading

# Messaging system
from message import Message
from message_type_priority import MessageType, MessagePriority
from message_handler import MessageHandler
from message_bus import MessageBus

class ClientConnection(threading.Thread, MessageHandler):
    """
    Client connection object is an abstraction of connected client.
    
    This MessageHandler simply forwards all messages to the client process.
    It also reads messages from socket (client process) and forwards them
    to backend's MessageBus. This means that ClientConnection binds backend's
    MessageBus to client processes.
    """
    
    def __init__(self, socket, message_bus, logger):
        """
        Create a new client connection
        @param socket: Socket object
        @param message_bus: MessageBus object
        @param logger: Logger object
        """
        threading.Thread.__init__(self)
        self.message_bus = message_bus
        self.logger = logger
        self.client_out = socket
        self.client = socket.makefile()
        self.client_name = "Unknown" # Client name
        self.message_bus_connected = False # Is connected to the message bus
    
    def run(self):
        """
        Forward received Message objects to backend's message bus. This method
        spawns a new thread.
        """
        # Receive name
        received_name = self.client.readline()
        self.client_name = received_name[:-1] # Removes '\n' that indicated message end
        self.setName("Backend: " + self.client_name) # Thread name
        
        # Receive dictionary
        dict_str = self.client.readline()
        dictionary = None
        try:
            dictionary = cPickle.loads(dict_str[:-1])
        except:
            self.logger.error("Client sent malformed message dictionary to the backend!")

        # This client to the message bus if desired
        if dictionary != None and len(dictionary) > 0:
            self.message_bus.registerMessageHandler(self, dictionary)
            self.message_bus_connected = True
        
        # works as a buffer for incoming message object
        obj_buffer = StringIO()
        while True:
            line = self.client.readline()
            if line == "END_OF_MESSAGE_OBJECT\n":
                message = cPickle.loads(obj_buffer.getvalue())
                self.message_bus.notifyMessage(message)
                obj_buffer = StringIO() # Reset buffer
            elif line == "":
                break # Client closed the socket connection
            else:
                obj_buffer.write(line)
                
        if self.message_bus_connected:
            self.message_bus.unregisterMessageHandler(self)
        self.client.close() # Connection closed -> This thread dies
        
    # Implements MessageHandler interface
    def handleMessage(self, message):
        """
        Forward message from the message bus to the client.
        @param message: Received Message object
        """
        message_str = cPickle.dumps(message, cPickle.HIGHEST_PROTOCOL)
        try:
            self.client_out.sendall(message_str)
            self.client_out.sendall("\nEND_OF_MESSAGE_OBJECT\n")
        except:
            self.logger.error("Socket broken! Client '" + self.getName() + 
                              "' didn't receive a message.")
        
    def getName(self):
        """
        Get the name of the client process
        @return: Name string
        """
        return self.client_name
        
